<?php
/**
 * 个人用户登录业务逻辑类
 * User: 苏近之
 * Date: 2019-03-24
 * Time: 02:17
 */

namespace app\user\service;

use app\common\enum\LogCategory;
use app\common\enum\LogLevel;
use app\common\helper\JwtToken;
use app\common\helper\spl\DateTime;
use app\common\service\LogService;
use app\user\enum\CacheKeys;
use app\user\exception\LoginAccountPasswordError;
use app\user\exception\LoginDenyBecauseTooManyPasswordError;
use app\user\exception\LoginUserNotFound;
use app\user\exception\UserNotFound;
use think\facade\Cache;
use think\facade\Request;

class PersonLoginService
{
    // 密码次数限制次数
    const PASSWORD_ERROR_COUNT_LIMIT = 10;
    // 密码错误次数超过限制，限制登录时间(秒)
    const PASSWORD_ERROR_LOGIN_DENY_TIME = 1200;

    /**
     * 使用密码登录
     * @param string $loginAccount 登录账户
     * @param string $password 密码
     * @throws LoginAccountPasswordError
     * @throws LoginUserNotFound
     * @throws LoginDenyBecauseTooManyPasswordError
     * @return string
     */
    public static function usePassword($loginAccount, $password)
    {
        // 获取用户信息，如果不存在则抛出异常
        try {
            $user = UserInfoService::getUserLoginInfo($loginAccount);
        } catch (UserNotFound $e) {
            throw new LoginUserNotFound();
        }
        // 检查是否限制登录
        $userDenyLoginKey = sprintf(CacheKeys::LOGIN_LIMIT, $user['user_id']);
        if(Cache::has($userDenyLoginKey)){
            $exceptionMessage = '请在' . (self::PASSWORD_ERROR_LOGIN_DENY_TIME / 60) . '分钟之后尝试登录后修改密码';
            throw new LoginDenyBecauseTooManyPasswordError($exceptionMessage);
        }
        // 检查用户密码是否正确
        if (!password_verify($password, $user['password'])) {
            self::handlePasswordError($user['user_id']);
            throw new LoginAccountPasswordError();
        }
        // 记录用户登录日志
        LogService::write('User login', LogCategory::USER_LOGIN, LogLevel::NOTICE, [
            'userId' => $user['user_id'],
            'loginIp' => Request::ip(),
            'loginType' => 'password'
        ]);

        return (string)JwtToken::builder($user['user_id']);
    }

    /**
     * 处理密码错误
     * @param int $userId 用户ID
     */
    protected static function handlePasswordError(int $userId)
    {
        // 获取今天内密码错误次数
        $timeToTomorrow = DateTime::getToTomorrowTimestamp();
        $key = sprintf(CacheKeys::LOGIN_PASSWORD_ERROR_COUNT, $userId);
        $errorCountOfDay = (int)Cache::get($key, 0);
        // 判断是否超过限制，超过则重置次数并创建限制登录标志
        if($errorCountOfDay >= self::PASSWORD_ERROR_COUNT_LIMIT){
            $errorCountOfDay = -1;
            $userDenyLoginKey = sprintf(CacheKeys::LOGIN_LIMIT, $userId);
            Cache::set($userDenyLoginKey, true, self::PASSWORD_ERROR_LOGIN_DENY_TIME);
        }
        // 错误次数统计递增
        Cache::set($key, $errorCountOfDay + 1, $timeToTomorrow);
    }

}